package kubernetes

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/golang/glog"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	authapi "eccgateway/pkg/api/auth"
	rulesv1 "eccgateway/pkg/api/rules/v1"
	"eccgateway/pkg/util"
)

var RuleEndpointControllerImpl RuleEndpointController

func init() {
	RuleEndpointControllerImpl = RuleEndpointController{BaseKubernetesControllerImpl}
}

type RuleEndpointController struct{
	BaseKubernetesController
}

const (
	RuleEndpointKind = "rule endpoint"
)

func (c RuleEndpointController) List(w http.ResponseWriter, r *http.Request) {
	labelSelectorFunc := func(labelSelectors map[string]string, info *authapi.RespToken) map[string]string {
		delete(labelSelectors, groupIDKey)
		delete(labelSelectors, userIDKey)
		largestRole := authapi.GetLargestRolePermission(info.Role)
		switch largestRole {
		case authapi.OpServiceRole:
			break
		case authapi.AdminRole:
			labelSelectors[groupIDKey] = fmt.Sprintf("%v", info.Group.ID)
		case authapi.UserRole:
			labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
		default:
			labelSelectors[userIDKey] = fmt.Sprintf("%v", info.UserID)
		}
		return labelSelectors
	}
	c.BaseKubernetesController.List(RuleEndpointKind, labelSelectorFunc, w, r)
}

func (c RuleEndpointController) Create(w http.ResponseWriter, r *http.Request) {
	isAllowedCreate := func(info *authapi.RespToken, largestRole authapi.RoleType, clusterGroupID, username, userID, groupID string) bool {
		return clusterGroupID == groupID || largestRole == authapi.OpServiceRole
	}

	construction := func(requestBody []byte, username, userID, groupID string) ([]byte, string, string, error) {
		ruleEndpoint := rulesv1.RuleEndpoint{}
		err := json.Unmarshal(requestBody, &ruleEndpoint)
		if err != nil {
			return nil, "", "", fmt.Errorf("unmarshal request body failed, %v", err)
		}

		clusterName := ""
		if ruleEndpoint.Labels != nil {
			clusterName = ruleEndpoint.Labels[clusterNameKey]
		}
		if clusterName == "" {
			glog.Errorf("create rule endpoint failed, the rule endpoint does not specify which cluster it belongs to, user: %v/%v, rule endpoint name: %v", username, userID, ruleEndpoint.Name)
			return nil, "", "", fmt.Errorf("create rule endpoint failed, the rule endpoint does not specify which cluster it belongs to")
		}

		cluster, err := c.GetCluster(clusterName)
		if err != nil {
			glog.Errorf("get cluster[%v] failed, err: %v", clusterName, err)
			return nil, "", "", err
		}

		if ruleEndpoint.Annotations == nil {
			ruleEndpoint.Annotations = make(map[string]string)
		}
		ruleEndpoint.Annotations[creatorAnnotationKey] = username
		if ruleEndpoint.Labels == nil {
			ruleEndpoint.Labels = make(map[string]string)
		}
		ruleEndpoint.Labels[userIDKey] = userID
		ruleEndpoint.Labels[userNameKey] = username
		ruleEndpoint.Labels[groupIDKey] = cluster.Labels[groupIDKey]

		newRequestBody, _ := json.Marshal(ruleEndpoint)
		return newRequestBody, ruleEndpoint.Labels[clusterNameKey], ruleEndpoint.Name, nil

	}
	c.BaseKubernetesController.Create(RuleEndpointKind, construction, isAllowedCreate, w, r)
}

func (c RuleEndpointController) Get(w http.ResponseWriter, r *http.Request) {
	isAllowedGet := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		return largestRole == authapi.OpServiceRole ||
			(largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
			(largestRole == authapi.UserRole && userID== info.UserID)
	}
	c.BaseKubernetesController.Get(RuleEndpointKind, c.getResource, isAllowedGet, w, r)
}

func (c RuleEndpointController) Delete(w http.ResponseWriter, r *http.Request) {
	isAllowedDelete := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		// op_service will delete directly, so there is no need to allow
		return (largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
		(largestRole == authapi.UserRole && userID == info.UserID)
	}
	c.BaseKubernetesController.Delete(RuleEndpointKind, c.getResource, isAllowedDelete, w, r)
}

func (c RuleEndpointController) Update(w http.ResponseWriter, r *http.Request) {
	isAllowedUpdate := func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool {
		return largestRole == authapi.OpServiceRole ||
			(largestRole == authapi.AdminRole && groupID == strconv.Itoa(info.Group.ID)) ||
			(largestRole == authapi.UserRole && userID == info.UserID)
	}
	reconstruction := func(oldResource interface{}, requestBody []byte, username, userID, groupID string) ([]byte, error) {
		oldRuleEndpoint, ok := oldResource.(*rulesv1.RuleEndpoint)
		if !ok {
			return nil, fmt.Errorf("failed transfer old resource to %v", RuleEndpointKind)
		}
		ruleEndpoint := rulesv1.RuleEndpoint{}
		err := json.Unmarshal(requestBody, &ruleEndpoint)
		if err != nil {
			return nil, fmt.Errorf("unmarshal request body failed, %v", err)
		}

		if ruleEndpoint.Labels == nil {
			ruleEndpoint.Labels = make(map[string]string)
		}
		ruleEndpoint.Labels[clusterNameKey] = oldRuleEndpoint.Labels[clusterNameKey]
		ruleEndpoint.Labels[userIDKey] = userID
		ruleEndpoint.Labels[userNameKey] = username
		ruleEndpoint.Labels[groupIDKey] = oldRuleEndpoint.Labels[groupIDKey]

		return json.Marshal(ruleEndpoint)
	}
	c.BaseKubernetesController.Update(RuleEndpointKind, c.getResource, reconstruction,isAllowedUpdate, w,r)
	return
}

func (c RuleEndpointController) getResource(name, namespace string) (interface{}, string, string, string, error) {
	client := util.GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/apis/rules.kubeedge.io/v1/namespaces/%s/ruleendpoints/%s", kubernetesAddress, namespace, name), nil)
	if err != nil {
		glog.Errorf("construct request to get rule endpoint failed, err: %v", err)
		return nil, "", "", "", err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get rule endpoint failed, err: %v", err)
		return nil, "", "", "", err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, "", "", "", util.NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, "", "", "", err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, rule endpoint name: %v/%v, err: %v", namespace, name, err)
			return nil, "", "", "", fmt.Errorf("get rule endpoint failed")
		}
		return nil, "", "", "", fmt.Errorf("%v", status.Message)
	}

	ruleEndpoint := rulesv1.RuleEndpoint{}
	err = json.Unmarshal(requestBody, &ruleEndpoint)
	if err != nil {
		glog.Errorf("unmarshal response body failed, rule endpoint name: %v/%v, err: %v", namespace, name, err)
		return nil, "", "", "", fmt.Errorf("get rule endpoint failed")
	}

	return &ruleEndpoint, ruleEndpoint.Labels[userNameKey], ruleEndpoint.Labels[userIDKey], ruleEndpoint.Labels[groupIDKey], nil
}